package semant.secondpass;

import java.util.ArrayList;
import java.util.Enumeration;
import java.util.LinkedList;
import java.util.Stack;

import semant.Env;
import symbol.ClassInfo;
import symbol.Symbol;
import syntaxtree.ClassDecl;
import syntaxtree.ClassDeclExtends;
import syntaxtree.ClassDeclSimple;
import syntaxtree.MainClass;
import syntaxtree.VisitorAdapter;
import util.List;

public class ClassHandler extends VisitorAdapter
{
	private Env e;
	
	private ClassHandler(Env e)
	{
		this.e = e;
	}
	
	public static void secondPass(Env e, MainClass mainClass)
	{
		ClassHandler h = new ClassHandler(e);
		
		mainClass.accept(h);
	}
	
	public static void secondPass(Env e, List<ClassDecl> cList)
	{
		ClassHandler h = new ClassHandler(e);
		
		// monta hierarquia e busca por ciclos
		h.findCycle(cList);
		
		for (List<ClassDecl> cd = cList; cd != null; cd = cd.tail)
			cd.head.accept(h);
	}
	
	@Override
	public void visit(ClassDeclSimple node)
	{
		visitGeneral(node);
	}
	
	@Override
	public void visit(ClassDeclExtends node)
	{		
		visitGeneral(node);
	}
	
	@Override
	public void visit(MainClass node)
	{
		Symbol name = Symbol.symbol(node.className.s);	
		ClassInfo ci = e.classes.get(name);

		// main class possui apenas 1 método
		StatementHandler.secondPass(e, ci, ci.methods.elements().nextElement(), node.s);
	}
	
	private void visitGeneral(ClassDecl node)
	{
		Symbol name = Symbol.symbol(node.name.s);	
		ClassInfo ci = e.classes.get(name);
		
		AttributeHandler.secondPass(e, ci, node.varList);
		MethodHandler.secondPass(e, ci, node.methodList);
	}
		
	/**
	 * Aplica ordenacao topologica e define heranca
	 * @param node
	 */
	private void defineHeranca(List<ClassDecl> list)
	{
		if (list == null || list.size() == 0)
			return;
		
		java.util.ArrayList<ClassInfo> cList = new ArrayList<ClassInfo>(list.size());
		java.util.ArrayList<Symbol> baseList = new ArrayList<Symbol>(list.size());
		
		// monta lista		
		for (List<ClassDecl> l = list; l != null; l = l.tail)
			cList.add(e.classes.get(Symbol.symbol(l.head.name.s)));
		
		ClassInfo ci;
		
		// pegue alguma classe
		ci = cList.get(0);
		
		// enquanto existir classes para definir heranca
		while (ci != null)
		{
			// se nao tiver base
			// ou se classe pai jah teve heranca aplicada
			if (ci.base == null || baseList.contains(ci.base.name))
			{
				cList.remove(ci);
				baseList.add(ci.name);
		
				if (ci.base != null)
					ci.setBase(ci.base);
				
				// pegue proxima classe classe (se houver)
				if (!cList.isEmpty())
					ci = cList.get(0);
				else
					ci = null;
			}			
			else
			{
				// caso pai nao tenha heranca aplicada, temos que analisar classe pai
				ci = ci.base;
			}
		}
	}
	
	/**
	 * Define apenas hierarquia, mas ainda não chama setBase
	 * @param node
	 */
	private void defineHierarquia(ClassDecl node)
	{
		if (node instanceof ClassDeclExtends)
		{
			Symbol name = Symbol.symbol(node.name.s);
			Symbol superName = Symbol.symbol(((ClassDeclExtends)node).superClass.s);
			
			ClassInfo ci = e.classes.get(name);
			ClassInfo si = e.classes.get(superName);
			
			if (si == null)
				e.err.Error(node, new Object[]{"Classe extendida não existe.", "Classe extensora: " +
						name.toString(), "Classe extendida não declarada: " + superName });
			else
				ci.base = si;
		}
	}
	
	private void findCycle(List<ClassDecl> cdList)
	{
		Symbol name;
		Stack<Symbol> s = new Stack<Symbol>();
		LinkedList<Symbol> cList = new LinkedList<Symbol>();
		Enumeration<Symbol> eList = e.classes.keys();
		
		// definimos hierarquia entre classes
		for (List<ClassDecl> l = cdList; l != null; l = l.tail)
			defineHierarquia(l.head);
		
		// montamos lista de classes
		while (eList.hasMoreElements())
			cList.add(eList.nextElement());
		
		name = cList.getFirst();
		
		// olhamos todas as classes
		while (!cList.isEmpty())
		{
			ClassInfo ci = e.classes.get(name);
			// ok, olhamos esta classe, podemos tirá-la da lista
			cList.remove(name);
			// adicionamos a classe na hierarquia atual
			s.push(name);
			
			// se ha quem extendemos
			if (ci.base != null)
			{
				Symbol superName = ci.base.name;
				
				// se estamos extendendo alguem na nossa hierarquia, entao temos um ciclo
				if (s.contains(superName))
				{
					// temos um ciclo
					ClassDecl node = null;
					
					// encontra no problematico
					for (List<ClassDecl> cd = cdList; cd != null; cd = cd.tail)
					{
						if (Symbol.symbol(cd.head.name.s) == ci.name)
						{
							node = cd.head;
							break;
						}
					}
					
					StringBuilder sb = new StringBuilder();
					
					for(Symbol sname : s)
						sb.append(sname.toString()).append("->");
					
					sb.append(superName.toString());
					
					e.err.Error(node, new Object[]{"Herança cíclica encontrada.", 
							"A seguinte hierarquia é cíclica: " + 
							sb.toString()});
				
					// para tentar contornar o erro e continuar as verificacoes
					// tentamos "fingir" que nao existe ciclo
					ci.base = null;
					
					// vamos tentar continuar, para isso precisamos precisamos limpar hierarquia
					s.clear();
					
					// e escolher outro nó para olhar
					// se somos folha, olhamos qualquer outra classe
					if (!cList.isEmpty())
						name = cList.getFirst();
					else // se lista estiver vazia, podemos terminar o laço
						break;
				}
				
				// a proxima classe a olharmos sera qm extendemos
				name = superName;
			}
			else
			{
				// se somos folha, olhamos qualquer outra classe
				if (!cList.isEmpty())
					name = cList.getFirst();
				else // se lista estiver vazia, podemos terminar o laço
					break;
				// limpamos hierarquia
				s.clear();
			}
		}
		
		// com loops ou nao, agora nao temos mais loops, podemos fazer ordenacao topologica e aplicar hierarquia
		defineHeranca(cdList);
	}
}
